package org.openhitls.crypto.jce.signer;

import static org.junit.Assert.*;

import org.junit.Test;

import org.openhitls.crypto.BaseTest;
import org.openhitls.crypto.jce.provider.HiTls4jProvider;
import org.openhitls.crypto.jce.spec.MLDSAGenParameterSpec;
import org.openhitls.crypto.jce.spec.MLDSAParameterSpec;
import org.openhitls.crypto.jce.spec.MLDSASignatureParameterSpec;
import org.openhitls.crypto.jce.spec.MLDSAPublicKeySpec;
import org.openhitls.crypto.jce.spec.MLDSAPrivateKeySpec;
import java.security.*;
import java.security.spec.*;
import java.util.Collections;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;

public class MLDSATest extends BaseTest {
    private static final String[] SUPPORTED_PARAMETERSETS = {"ML-DSA-44", "ML-DSA-65", "ML-DSA-87"};
    private static final String pk_44 = "B845FA2881407A59183071629B08223128116014FB58FF6BB4C8C9FE19CF5B0BD77B16648A344FFE486BC3E3CB5FAB9ABC4CC2F1C34901692BEC5D290D815A6CDF7E9710A3388247A7E0371615507A572C9835E6737BF30B92A796FFF3A10A730C7B550924EB1FB6D56195F02DE6D3746F9F330BEBE990C90C4D676AD415F4268D2D6B548A8BCDF27FDD467E6749C0F87B71E85C2797694772BBA88D4F1AC06C7C0E91786472CD76353708D6BBC5C28E9DB891C3940E879052D30C8FD10965CBB8EE1BD79B060D37FB839098552AABDD3A57AB1C6A82B0911D1CF148654AA5613B07014B21E4A1182B4A5501671D112F5975FB0C8A2AC45D575DC42F48977FF37FFF421DB27C45E79F8A9472007023DF0B64205CD9F57C02CE9D1F61F2AE24F7139F5641984EE8DF783B9EA43E997C6E19D09E062AFCA56E4F76AAAB8F66600FC78F6AB4F6785690D185816EE35A939458B60324EEFC60E64B11FA0D20317ACB6CB29AA03C775F151672952689FA4F8F838329CB9E6DC9945B6C7ADE4E7B663578F87D3935F2A1522097AD5042A0D990A628510B6103CB242CD8A3AFC1A5ADA52331F4DF461BC1DA51D1D224094E7ABED3D87D98F0D817084780EE80370F397631ECB75D4264B6B5E2E66C0586B5FB743516399165837A0FDFF7C6134F033BFA69C1B2416965C6E578592F40E258CB6DFB29FB8E0F54355B6E24A65F67ABAE3193D007115CC0B9FF94CB911A93B1A76C0E7662F5E2B20139E0159ED929CB932D4895F89A02E55C59DF2DBB8F6E5DD7D5B1F3CEC37B4A9166B381C5440E23E67368CDE0A29D59AA05A3C9BE24A4DC8DD75BE30E82BC635D36AAC66DE880C6701A987D7E05F0F2FF287828BEC30595089D8AB9AA390ED719CAA6E576CDBBE9B184A322E5E2DABB69C23CC696D54FC32FF57001B6B64E2A837F3062D85AEB50B3510F7EDFC34DF38E083D4D9B94FFAB0DE15D73D9AF30B9F31CC4F41C9C24F2D618B2A7C3C4BDFB745D52D3EB54589C8BDA8AC05DAD14EC744505575A0988EEC651C1715439FDFB29923380A43C1A66A86C982A841F11820A6A0E1E2F2FFF5108ECAE51A6AABC9B949226D228FF84C4E5E5D63114D80359C4931E612DCED1838B7D066AC9182CECFA223A21A4C8E155AEFA780373BCC15098AEE40C033AF22F8E7C67A0D2526DA7475E830308C04AED9D32BCCC72E719EE70A8D13F09AC11E26EA237D5CC8F98B5AE0E54F933BD0507942ED900D056FD32F8E6E81777912FD482746029B71CCE3BA69B8FC2D03EB441027C387BC2F95031A0AE7052215EB24B9EA8FB0A961B0F80BFA80D0D6257C1C22B508C5D31B97FCDFE1D1766E8A9C8771932DD598ADB7E717743F45FC571F21E4A516249F81D747F15329790F0F70A0B8E461A4EDF50504AF03F30DDF8A8818E38761E1681D6DDEF0B1DD326B2EC228CE48570F285B49D29D7C2EF37866D5446DF82B8E43B34CB248962A21A9A3946159740F8AEE8E6A16A4EB2B42D143FE2612E05EF4B5E646D813248444556A2A8BF92CE10BADECB6B8A40B080DD42D53346FEFCC4B9B40B1E4998991EC753C95AA2F2A506F311E710B0F1D36C1DCA6644EE6D1D4AE9CEA5666EF4B3E888DBDBB95A77ECFE1E8B477DE7CB07639D682D53020EC14EA6C7DD7E715389D10938429FAB8A068A1466A4CD891359F8074E0F5A142ADD731B87878D985E4FA6ECB3B73D298553418273E9503AA84092C080E5F2902F90F5C59944D24CA0271D11D0D6734606D039550A37FCA2B735850E63F540F2F06B79144B5C4ED2C700BB51C33D265B3D037389C99EFD597642D829DB1EB58643CFCD07F4DEC60B8F727D97BD7C4B59BDA1";
    private static final String sk_44 = "B845FA2881407A59183071629B08223128116014FB58FF6BB4C8C9FE19CF5B0B28965F58D99EE0DE7BFB7840F59F65414289E259E05E8A18D47EC06D7900284AD7A0AE404B93B0498945ABD07638920FC7F8C21282031EECEB0CC48D71FB0AD1CE84B9B3DDD1B3A2A40A15D5BFE0BE23FC4757137C6C8FEB7F8055677BA4AF2814242A9AA0108A4046981800042130A2340A99140E01A931E1300A1A136959486601C111140451244206E4C848A1B80008B2490121901224500B03306112025C144912B30049424D14470E93342023898803238CA1C081CC0411184545C1C20862465210300D52B870A3B004DCA20D4A386511214A01B48C43104809461253068161348618A00920878CA0168682208E41188E52A29021014652184521A42902132ED0B8248816710AB245818480942670DC126C1BC16C4016246236008996108B124E1B3721639225E1C88CA346285A32111C856110B9295990910493651CC40063B810C1184A52900C1CB20D2380090A4021D4188043A64D0835060C98650CB4890C8661D804601C0789D09231D8081100881119294E48B661D8962DD2B04121A1051226860349301A246D99483001200C5CC2840128900A298850340E1804640AB409D1968413196DD91031133689808025E300294A06304448109B429008858902A20D13461110120EDC380C4C468842C004131490C1462248B82D48C80D64346C02348109136842A4211B26511C302A6328688B18609C922C22C9000031058AC08C894430D0142189424C4C106400B64DD2A8246306308BB4289B246EC34262A0C00C0C2050E112444A360AD122320C36014836804C00400C16090C9569A19829110602CC982D08C811498445D20290014942C1062E42C000A2902D9810711A054008058608B2651B36441109402107100B3306D298858148088034305C3844DC266ECA947004394A442841E1464898862D113621221509530826213088DA94804A80601425321295641B02120A92241A4422230624CC12818922600036608A32820CC368A1A04D5B286A110744589851C42831A4064EC0C831CAB46810B669132488840462A3C421E0920523303050B6105942285C3005102286CC260E2226450003928A242C9C024E40B20400148C4A086E4BA6444CC84924464D4946821AA4690A316E21408C22964949044618B72C12B864D1C851C3366E5C2008E3328513198564C2019A4822111300A4B64960C06D1944628C086119B761021570EA732640984F68143D81AB95763698DCD02625A980994C19B7D47626409370F87E45D39B76B6762F7827B2779BA44046F54228A7B1BAC9D8E751AC2DD248521564F81D028AD671DB6A3A5BA3A9463597A519AE0C1DA435FEF7C421CDE7F8483782C4334993192923796DDC3E5803F6C86CF2B37E39BBCB53775E5E9D2BDC65E84395B01AF95C4398503A7E63A8D72F7E9BC58D7655CB86BAFA0B679D1D791F09703F6E4B333A51DFB14B36EAC9A3582F795AFCECC2A67A42C7BC43B447B73EC8B73C9164B2D9EF4F767D607E975738E1D871FE61B7BB9570516959B9E7D8155455FC91590D84BD430921568B718241CCD09C989DDF75F529C4F15818969216EC0FBABEC64F184832BFE428BE44849C42F13CA84A5CE6CA628044CB9DA3FD9CB5EBC776BA0E12683B98253B213B286593779EACC489C985BEB734A7506791BE1A6E6E03B557CFEF2069091B846CDBBB72D36A89FD5C45C7C7FBFAED956EC9D8F3025225D6FEF811B1CC3C6705F056388BBFBBA1FEBF4D072281DC792ADA048216D60D0D6044150909B70A4753336F4D5D3FDB8BACA62946DB28332EC778A76F96577BAF868BF896773C200637495098B0F7749835915348D37641EC74D5A78E565D8100627468B0B5D05C2937618A98E5A12476533CE9D699E0F4F23B3127EE1F8D3CDE3851A94BEB8D606D836D47A9F090581B660FF8EC31E3EF4E45DB530DD88A44D48F84B6BD3CED53BE935EFBCE9930DD83BD004F086A064B3260B7211BA0547CB29E56603B922597B31A30A5FFCBD8A7AD2F3F05372DCEA89048C683164410E56A6E5B7EC2EA6E3B0176DB1EBF9258C97C297E752A202B1B4B02DA47235873CC3E4A376B655779AFFFC9AEF2E30B62BE022AEFF5E34B468507C921A00F1110B2CC1BC99B2EE295A31EAC141AB4D87EDC11D7FEE22A07D69B7008C107EBE47DD2F215D074868C8B929A3DF6614B5E95CC8A52F96C57E1697CEE332F45B1980E6CA372574BDB51825654E6FF31D8E755CFC15B602F46A0570D3BD0D65E43BDE8F792E7D526B3D362FE33638B3870E1C29E1030D3DBA43C3D0DB6E5F501EA3441B709E54FCA1FD3E8654B1A9301AD151A36C8FA1513D0229DE6E94D02A97B9D6F2EE8078A5D1228E783B8FDAC46102A19600C531276B447F3900EF8F723FC3B60D7BBBBDA9F949E4460B2F1103315ACE25B3497CA4B4674A6052304609A16E90D064B0F6A9EAE8D8E4BE38E3B781F9173654E876CE6CC00578171FD9CFDBA5704AF2E60CF84FD02EBEF19AED140287D195023000568E8D908EDEADC76BCA3E7FE8FE1F4B201C2A15603C025A61917AE664B4294C57030AC988D7967364A8BB5857E98218C2943E8CF1CB8C719DACCFAE8F42AA7DDE9B4130170068A9CC86B0552D1DD0A48F63DDB8F8A51B3C0E10F3174A87D18F96533CF0C30C0BE33D37C25CD3EBF8F5F10E66464AC2FB8260BA8B5BD443ABC6AE0D5F89DAA42BC71B2A9BC2D10140B117E32F41353FA1ECEC9DBCCB621350806C4E4EBFC966AA7122D85532728EF26AA40E55E2E1739790FFA157EFA4E2A68DA1EA4C9E3273CDE11DB7DA47220123C40D50B73B8460CD93CB449783EAD8A58F6FF272275A4F6C0437DAF3DF1D8D9617743D9302E73B715AA7CAA139CDD07EDAD2DE7B32B3F11E3C2B6A96D4630632C6D1DE21473A287D42B52D210CD4EF808A998148817BB5083BDAB3A3354B084C5773970B15DA56F755E1F25354EFEFF89FDF4E307998D623CED32C7B9E942B6816A87A233867E2BEB84582FFF61AF612BE6E521AB3005E17D471A5C67A0A927211802CA4A0C2FEDC01A12AF5E8A6E8FF7BD08883AF1D35C2F8570091D9E5FCD9D720644F0A45042443D2FAC309FA7A15B2DBBA1517EA19407B17CAF7E9EA81FBCE42F35A0568B2F70B279E12DA48858F0E0FF0B5CB3A3012308AB83DB627F5439E3A424D83F9E0B7750F2CBD9CA4AFB9BA77765A1EA590CE9E37BC7EABE1EF099765CE59EA74C2776ECCBCEB895997F577733C609B9E853CF03010C5B9CC4E819C041ABF11A9B3866502BF6AC0354E6CE1060D5155E6E5A1AC4FADF73080B20569317F9E118B97C78D7273300E329373E5B64A4181C4AC7B8933F82ADE505D52CDDCEBA373A54D072BC04A8D83273586615ADDC4D6F258BFD75AE94EFD120F20B721A1CAC33FF3446E554D6CFE266B05E4CD44DE77D263A9D347F53B282A7BCB1511DD405A67AC05E7233F5EBB40D38F953AC903F90E96F17A6357EB59ED7900FFD82F54C3043D37F7774A0F4DEFAF65887EB53FE156FB26794982B1249BAD27C89C136097AA2268347FC9C5E5BE79E23C923215353A850E63748E766";
    private static final String pk_65 = "43AD6560D3BB684667A559EE6EC7C816020E5B65671F270F2353A8C912B6C26B0DB0C2CF42DC747B10AA3EBDD573B300EEA46C4200B210094F9512119A6BB837242762B2CE94C2467278500EE7B139BED906676663355B813A9AD9D3DB70F7AF2D785040BFD51208BD3D2CFB09EAF7CEDF77D1B59DA75F7728F120C11898D9EC2CB22C73EB8F9436FF60524B56EE6B413030EB7DD10774261452CD8C5ADE75D1967628078CDA77E2B1AFB83B9F07F6939D37FF54D5E10ED17FF8A3C21546A89F514576AE780DE8761C4F2EA28828C69E38C730ACAA4CC8DC7DF63BA4C1525510FAE2C8E1B01812358BC5DFC01E955294A5DFDD1CFF0519E20B8F74FE18854D80C86051AA5CC2FC1DB078BC785BF4BAD6832B8C269156509B332038B4C3719DC49814FC6B6AD5360E945AFFF4D4AC235F56C7F7A9A872B518C1F0D48184DA0EB318F74EB84C4F324A2BD03704D2E2A59F64A8854C7AEFB2D3530E20C8AE8A487E6CBEDA645BD86A5A83E77A6A22888ED8E43A7F4804C2DE187F1ACBA3CF55CF99412A7A59CF77A4A977724A72686FDF7FC64492A5CB75921AD014EB727EDA1DFA7BD7ACE52FE292322F0BE0B004DCE44BEAA20FF06A7691DC36405361F9240DDF2FD1A5EC422ED639505AB8E137B971D5729B11E84C040247424A51DDDBDBC43AF261D038B0CD70D5BF44252A3786A26AF3FCD4EC100E5CDDE019F17BE6A64F820C3F622F78D4F56A984122D6FA2D438D548DD87B9095F1FF02437854E2419A0316C33EAFFA0161737E476A9E707CC40E78686D6A043DDE962B319BE2BF9F7A1EFF9EDEFD1B4CD07131494C084083BF76181E3EB1399929314473A75E199AC9D5444DB0CEC07E625EC70C6864093961950987FB1E96DCB7E001209865D66D829CD2E2B240818CACE003C9CC74DCE5151C65E59AC1EF6D495B0C717B4412C70B50CF44F44E648788F46BAF6F8AF3361F0E4B6119EDF6374DA596453169B935E1A3B875A6C1B9FE384AF961860514E8CF291D8650D7530DB42A46790649B5D8134AAEC33A41F0AB4296AE26203291F1C2BB5276AC305269778E7F2A4BAC15B5A31A6B6B76342596D39C7FD3D1C518689372EBD20B667BE5EE2ED11BC107A7600EDA1BE7A5DC05BB9F16D2B8BB1C7D8D10050207530BFFDAAE7B11E0615726F2E99CE99D6CA6048F9D61B14F7265473EC2D02989772B3D7E212AA68D89374C6CAF7AB160C6C5E09502049C3D03738D700457F706341DDEAFC6CA739ECFB4F193EA6B385B035EEA0F7BFD61FA776AF32AED6366E6C0642D1A01759FA6BDD295F7D18CA6DA1D48563EEE403F2F8BCB6A60326C481F12F8180B2B8117ADE61C7E29F5254207C5D4657B82BE4EBA436752EC7DA0627FCED830C15F10FB8D3CD90B4505FA325B54D954C5B6301DA72B262B226EAB2E4EE88226CC606B97736260ECB6D8F74A0440AFD5D751A90873FFF00C8D3E9CA0975F303F7AC263B8FF496C6C8FD22E8EF7B587BAB50A7DAD99BD55D3B7968584F1FB21255EE22DB56AF6034F3F13E659161A57CA8C9F2E87CA96BD7100FCEF8F74A8C6A1C92E2EFF74E2F5FAE512C09D26E0F3985D882401EFC54727BBC0F4E1110771A106898692D0C5A6997CA742846FA4D49E8ABDF123D92743E9949BEB6E46B9655EE698C23D74991C96067DEF06EACB981AD4A7A5ED91EACF05D374C74C443F3FBAD363B2450A1A47AAF2954D36E53B06345139138D38B941298982EEA84400C4DCC38F5127951906EE3E40F75A5DFE09FCE9BB0143A5D5ADC3C402F23A75A423AB98392CA3A4D5D23D3BCD56FF22C9612A5D2C223C7079958CD05175AA74DDD21B42051CDBDC14048CB43CB2F6535E2CA9F5B87052F633976F4795CBA69D39F2481CFB9D210C9B0E9EFD941AC875A9A6C3E839EC54F55585721DE41815DDFC05E8A58C97E2FA52984135AAB0931094FF8400CAB043C2A5E63C2942B7D36988C4ED9B73C11D913E758ADF94291A42743E4FB04C271ED5807EA03271EA6656CF967AB2595588B55F82AF2D07ADFFCCF859ABA70B1707B722DA1FF393CC5BBCC02014C0D4500655577946DB5F95EF1E7657DC98402E5CB048DCB372C9277FDA4D8F3A30C953822474CEEDA670D5E680029259260D91F8737CF7572651FB28A7DF46F671679BDD507696B021C2C7F4300F3098FF9460582DB58E122C585185BCD091E7ACCB608F7E0C3558627484529A662C0528D419248B6565D32ECC78F7891DB5BB1984CDE89C1AF25F0927205E734A7DFEB9AEE94F23F2FD11FB53EC768F6B8268E00E4054CD12EDE4832B07A254A4E2241854E8FF2AE1E1B248F9EB1C77581CA2A2EF2D4C9171177A1E040F9D4AD8D0D0C6CD14FCD13B233794E51704B6890C56BCE1B8CD1C9EAE6D59ACD91EB67B3A618D65F0F94E5458271E14DC6F6530AD0EE8B2B2F0CEC14612E563338E241602B997EC4E62C83942C7F18DAD6841B1348CAB99A78F598FE78A20205D88D826D2E163F6B628B266C187B427F253000E4EF99FEC0494A97D9B42E37EE613767D2651FB7CB2B9E99578CE2D78B9C9777C954DBD1D7BE8B568F88AB42DDFD293BE28747103B052AD81D8F6254E426802516500111ADF0A8F27AE7C55D3D5DB86278FAF58B68A26D12B2801AC28EDA87AA5D692EDA9BE08F7CC3E78517299A3FD9CE2A0A893E12D71062AE2514C465D399F165E4D2F71D1913D8B95396681486432B090F0CCE86AA84B661FF22D4A56035E821A1CE30F33AFEB6C7B8FA9CE";
    private static final String sk_65 = "43AD6560D3BB684667A559EE6EC7C816020E5B65671F270F2353A8C912B6C26B3DAC8325D5208FBBA385BE4D9C77611961D796200072F900727D715515AFE3A3839104C526A5806257D54D22CA1035F17DCA06D2CECBCFAB310C994F7D58E62CD8B9A5CAE7FEF2775FB612246459A94DEB8BE511BAD4C007C5607FE1C9CDB7C91871042734805760071827016058751083551404015714520370521746384075612325660115585060271425754524300210322561021486801010162177780477202683415726137828571450344171475716007373235645487001382355682365241402741446735211762873740822880881412810353573148554078386048742028384216832477015164257364347170070476826425234761712102353485186636564574447243771201641836834385432107528153272134368623722717823427554560072672463206348216547035015641236782111200887127213301537550124661416832623134008275127301110438274170284760336777540767006607015458542733644775276530314810335267270536635024462712033307648853181526103811626342733747741152737243631230364010563017427324681651021264141247752460772130064237162780040136355137636372534687431201613276233550463451322058364785885052601380733632615061100465258855854864707833387285270253717572283677208267866324426488886645676412153434442408831512432143710810473648751735011822833662424478745475884038716125567184208768860650416622351137275746336756553777453220673618100250174607842037110877466573677678706353178267287086771186367745572287304642684357468807241650421456533613420681644021216844631405340281021861520627758706484238277807350637843857572806363145528876076540884216485115253516510151067614034047278055348333071554060501056777512544758313056213375015458472237554531301468550435046337758252140465058317826818578051023048434771488433303371860434612207682051681007274235504651051105036485727048777861140347683323582177270861101745674724017721068503801314751007050330681375451457311657155115524167058165654376648748467456025718674620018777736488172045365831431275647364001826642826734268570001877424401811743854565183268240488347571103622607068764060037426823556464876211866704285442566432428611512475760138184868586371047026434127363468435820656468354150167168553621221763128784271170314625350386132370263571064140316816542013815265271072675687266120566678877825231880065251181231633822285122458443015318633364000784236458252384030804427720857853028048800238108156324700020705834462866200751015435325642622554202314710477450768266688562681300811782175711530744484640405743387208555323670078410875477644815740410477021318650866587571047373875644673507143062246681336181716752823720657328628827480281562757760882401671872655304836836112543587554817442804721806221028288400170254073756017317640577647108071763844565245115036540662834220751655287342221621752356237133878057213237585028728713738001774675108141608126487445165350021324223054841153544842584453322153837566711443274846273473770210668325262283138640602101707206151144184503676815802138832302751472227117561542036262326615431338165116758685720162285157116377643231431243852733776584261054544005228457187605483018422535700074218213083153112480627325618021284611307255002061572654863673186621261024440273457334313817453457750444B7169D30417F278FC99324BEDA10F1E123855584D14CB6C067B8E8D699C0F991FBD7BB1791F7B30DC418483BB316CF9613E5BD127EA122632DB2F204D78DF5278020117CDA39DD5A0EFAF087C794387A7EF66B8420B966B2326A802B34A3E92F71BED842371FDAE6B0D61D5C72DAA85AF99FB4C97B14E2E4BC4BB11D3B3434ADA822CD786F43DA8026FF857FA82D2C32715DE8DF951855EAA8644B2BDE0CD5E8F8B40624DE06C6BA60C4E50308A757C1B7B24B635DE0CFE2EFA64F14AEE1DB19A8A834C2C5B9C45CFE2D1842DB27B471E70D176735E05D4279270CB78D460834CDA4A25E344E5C5E1A03A224C3857E4912F944AE618B1F8387E8B2475C9A20D59694060BDCF3D83ECEA1A1445EE1A33F9C2ED52C39642BCD39C1D4B09062D121D028B220491A6B3C66B81442C5AC20B1BD3A047F1E57330C0FEAC6FE5CF725FC76DD3A84EC4FA9C9164F2061494AA89B0367E4A24B08FE656397E0924EC373898F2AA25FC62A9E271A0DA015D4E4FF2189654594D574B8F8884DA9991A6F54A2F13DF15F8B0F67D7491207539488505D591964B4A1F22650E881E74C84255E81D016F5A99AE559686098997CCD1AD102BE86AE4A1CCED1EEB057CF89DC103CC4026E4B244640055AE44B08A85C3C3A0617998A17A533ECA6904677D351E1C10575032FE493577AAFBDF32EE4C8784A1DAEA2F04EDE5E6ED69F6CBE75D1830EBF74A3F93C13A199895A4C5528BBA9A3DDC3BE87EAD4E2FD9AA951A9F5749502CF89FCB57F47E95B479372BFCD80D0203C9680D94B43671C12B35C511A6CCC24A284B6563C6F0833FA5E82261831C4E6D3740135200CF6D10342A808AE47EB0CFBBE2E9912B7EC2E53E69C3873E21D52B3993E080B7926E95E167EA3EB9D3E52B802B03BBE35CAB65C938ACA9DC7C282A0FE2E0EAFC9E27A5945B2EF7279145DD5A4F4DD63C30CC9728CB0849D5BD35D43D604B42EC4D76C0120A38FF97A495C38D60F573FFFDC704367618B919791A6806A2B9DA834C668097911568C554A821307E5CC522B6E607193C1CB7952E221957F2684B4700B5AFBFCAD7852735AE040D63ECF483FA2ED1BC358A274E80695AD706B47BDF7B8BEC73DD99187B8DB51E6CF0D04BCE594BB7EAB045C628B45C195005BFFF6F43FC61C2D617FFFA5C06B195B7284F15D009800856149930309E693060381F6958CDDE8EABE65B87FFFC6623D239EA72278E69AD8C505B16073C8A035F2BB9458FA38A6EC27B7D30E9F40B82F657B21B4CAE98FCC7E366EB20199BEE79BDDEDB3629A43FDB2ABF59CD2E37CCF15C59234DF85B15A1C6440F3F66DAF010E6BB7A3A0C1DE62CBBA224BCEF2C9233702B92B124A29FE54E45CA2C7BC371D52D9A9E11AD0C84771A0181378AC896F17398E6583B84A3E495E0AEDA09875EF4B676AB813C8C9DDE120203BFDDFD9B6412F5A10A49109921185D4DA5D89E455B036654F6108FDF36701A36538EA04AF2855C8D2A6E7810A45BCD4F8940320C8C1CD179070307906E016815E9F37D3A32943E164DEADB8C1A393EDEE5ACEAC9C7153B55805FEF3A5EE27234EF040B8F269FB96D3659044C8CAC05659F3E9F603C3C165EA9749AA1C0A7BE268E747B57B01521CFBDCC63B3AFDC45CF5EC82431C6CF18A98357B1E1FE184818E8896674E867314626AA5A50B3333CFC3B1FA1E9B8BCB45C181A6EEA07FC14566695171D3EAF5A60E92A2ED2FBC13E34C699AC1CA57AD278471601892E9674E2763ED697F80EEA52B06A747AA4B0FD2DEB55D565CC13C7AEBC2805BA2777E19D884A0A618B08D6BBB4AFFBE8A1C1CF8DDE4258B11E50904F488EF7A73FD0E0C141F493BCD0C648F23DD4B721ED5CF1041EFD1AF46446418EFBEF514AF9714F7127E98B6EBF17755ED6C0EAEACD61400E07F93E885748E248578784B42660AF8936CD565AFF9DC16647C014000039E3107B978FF28B3CA0E6C8D552925D9B3B7F9190363089C4BD709CE8DD0AE6AF6DECD6D5975539EBFC011975D268654E0A7BBE49AAB559B01D9F64CA7E858BC8F78AFBF9E7B360F0A039A33ADA0AD70677C6C90AACCBAEB5DD384CE28126B2ED11306662819BC51E71E1EF93CC989244FCC0DF266D90FF57F09FB4EA2ADE80AF9C9A1A67437F6FCCCBBC9181F4BEDC7B456AD592AE4ADA35AEB613B50D46EB3DEEC8F9A5F36F1C92E900DFE55B212EEE6FC963E0C149B9AF83E256EA3021AF7BBA9AFF46E2617BEF211987F1C0BB6F63737BDE142C96C6D284421A3E49B141D3125580391AC5EEAAF7D3FF1A61CC15AAD4F64F712D0A26F57A869E28BBD44A056000819D9F8A42FAF0046517B3B8DC43F04B2F438F147462ABAE1CBBBCDE086CA358C3596C34BD237509625AC7033CDC40EF5EDBC43E0ACB41CDFB813761200837E47F9780F5FD5ADBBF03065DE78FCE30DB1C0FDA6C64BC13C98CDBBD518A73CE446967F077A5D965739C8664438EC770C14DB0CAED34E751BB3AFEA13B53E92E3CDEEB1AC151C628A44A6F93D50C10FE5CE305EA9CDAF9920F7AABA65A65BA7038CD4EDFD633B15B8EE96595F5BA90C3E7367C6FD8AE7356B7C870B3DF7CAD77A57A189472EEBC1634059928F0E44F8A113D8328001834AFCF64D589FBBFF7FA6409E4BD0A08D96B751549B2DFD94D7784BE7943CDDCC41149EED29C003CF2F3678C8258B3061AE592B5C46BAF4DCF3DED7DFCF0DF4D9CE1A37954356A7FD794E4AFB8A30EE0779CC9A28E72F0671D5E928E99E302AD7FC8864472004F782EA17249B803A134D998C8D0A52EBE3AF26C20AD3B789C324456FDC1D8B432DD92A3A7CA8AC6F0EAD3F714C410D462FBAEFBE9138227172D58672A12203228F3141BAA173EA2BA78B6152BF472FAA9A8F83643F19659AFCB7709F82EA91779B746DC8B4522424222F71C9579E6D1050386A4762BAA1A38CC4A379F0744704CDB8DAF847A686DA42A26FA7CDE58800E0701EC7B7980A10F7464A4BD6710988919AF04E0174B058793E655ECE6F31F41EE01C6C9DD3D6BDB9C26F51A0013B7E2ABF343986ABD9617DDBE0D9B760E2238C4AE3CA620C515AA2CCD5C5BD6D0BA2061CCDE43DCA83834F110FD123569CEA234656904C2F02F12FDEB8996BCE10792D7E447BCBA1A91900EE7A1DDDC3FF4E05AE59A267BF78550C79847DFCF6960EF3D908A4D28FECA3800682581CE766A64097A5BE69A6DD376637625C8F9E10DCC951056BCC843E16BBEB7C137E2D83680F96AAF8808EE24FB6898742380ACA5A23C6EEC15C0AB7D6B4D32678005CC5E0F042146ABBB52D13EE3C7D33CE78BB55D8FCDB4F9023AACBF04D3658C6A81DA63E11C1F53FCB7F684A66994C55F7D024EACFAC708966A665658BA599D8D781DCF39F69A36278B6CBF500CCBA29211CAF2605FEE01AD47C3FD875C8DD889F0C699C6C88B22FDAC5E5DA14ED1F6FD475D74B647BB23F2C0F66F091CCD70EE089CED548CF11CB6A11D237C8CB4CB549B8BF9904F781865F17897F05B40B8B8C9BCFA4DD2BE7D23";
    private static final String pk_87 = "18DFF392DEF5756EA23519A240E6B5CDCF912D89CD94DEC9DC71E53F8CDF37D96CAFF7EB6F182B85BEC2F534DCB918ABE8428E341DEDF6DB71B4B805767B6C9FDAE294BE7082D35FB68C926500F287602ADD34C3F95E2D63B48912AA476A4ECA076EB040B260303E412DB261F1D4741E742275FDE42156120B8BD6FE6CF4FD54EB2271F8466C73B20A7B230CAAF73C55B4F351D4E93A06940A4D4277DC0254AE825145C2E4A06BD33D822446A0DB08E0D1335CB0BFB57094CE81AD2B15E1F0D52B8E548170A8B44FF9F2B80AC88608DFD89489C114EE7C88B52ED2AAE5720F46C23265C67A24CBAA3CFE1358DAAD57D34CEA19B74AAF88FAF6B19250EDB3B66C6D272FA05F896A4763A1D60A5B859B214596A671A5832E0218C808D22FE2DF4AF850C638B81A682E68793F3F1CA8A5261D7104AB340556D66F40D5930AD669947E484CB965403FF5E57B935BA29DB5B97FFFF164E9508816E2CD811B8AAA768321FACAE3CE2B2A9CFFB2E4F1CB68CAE2FD672C314D327DCDF46A91F23929BA327AE8A8646FD20229FC5D85149484BCC473F52D5BE9A0EBC57E2E252EC895DCBA1CD39584CD7FC14E96201D90874211555C4BFFD9F906876F3E112CCDE00914E3E460D8E95FDF0C65F70C2325A1B077DB3CCD64F1BED863A8E82FC122746161696CE0A30C03E99296DD5ED031BA7920CC15BA3570BAC1BDFE00AA60A3AFEB73FDFACECF9BE1A9E118BD645A821CFC3E80C284CA1C1EE1F7B5EBAC2DACFC39D486F209F5808181127EC012A486C521A478F478E61B43E28EBF5C94E4E29827E33384D33BF6F95E32D824E28D3EEAD4A0736CA14C21818665A2B71FEC36E49FC5CAF058D29C3BEF0403AAA6B28A23D9663FFA8BD36659BF2FA4356BFA22CFD49EE0457B17BC9A67C4D653BAEC1E345751C6208E5EF0ADFF638044CF67ABCEC0B88FECE6FCB04E1016408A70FB734A13E14714E96AAF18215A4831DC009B7343839C728C9B9DABD96F472B3FBFF3DB4F27D82EDF014707DFD7C7E412F760637DD07CEDFFE1AA673B8AFB6CFBC1DDA4456863545336CF222D843361B1EC850E2003B41066A7FB799256653A8F46783E4E55B6AAA07B87DFC54932A0C64F1543A9912577BF61502D3BEFFF90DE16B456A49149F7F0BBF4F0A37718AA99FA4803A4A20E4DC8CB398BFC47CE13DAE175B78A6A5BA18200D35E775A55596E1BC0E83F878612DAF3BA51F0E844AA1B5DFC8585D693800D2059C9E9B6135E094A9AE2AE06A00C43FCBE43AB22A344C6DF8C55C9BF1EF99F97F6E29A1FD0AAE7D2D1031B578FCF10E8C831DF2D537560B274D4BB305AA299BA64EF162083C20341E81916F065A483DCB9AF74AC78BFC4D19C4C42B22688B171ED10ABC43009E39E121AFE25CBD16F929D9EB164E0B36F17C286E5987D80B43A5906E3315DD3BCBF4E526FBA9EF5106837588256E2A8F069411A8CBB7FB7109C8E152F81DDE2304A11AC54EBEA1A1EB3AAA20311FB72FF9C8CB230B45348B8B031A81F68C2AAB84E5F6F977038BDFEDFEF57E4FBFBDF30B804ED8F3ABEC91EEB252EBEC52311C48EA0F59C36EF01A138E86F740C972285D8A7FE9B817E3E58A01F5FE60CDA1247C351AD0ACAA765351FC580A873E8ABAA728688FF7C044827D600AAD3578F6E4869FD2E2F75ECF38D577E4E2011B1CA14BB448F53596C5AB7E40C42FD34F147002EBC5765FB5AE3DF9A094336F85CA475FFCF43B7A998EFC9499D8636375BFEAB91688B0632636B692E39AD0BDABB5851334C7AD94396A10817284FCEF3436C93731F92F7021B272D6B8A631F251860E161674BDE79B245D86E9C9CD9947AEACD1A2AC7B319FECE3411C8C27144274D33CEA8D1B9F22411ECE82512A3ABD32D31C678025554157F309EAD3300360F3EE2B873E10ACE3CDC36F175497F095E496EA81E34C2C7369B5722259735E0ECFE684E875B71C4828D2A463A279C2552019696829AA64BED7F4C8E2442100901230469A9ABAAC443069B30536405CC3C25A11900DAF73D598A131E329CA35E6E7D8071DDC0A539ED9D7A4BBCD512595A931086128EE9387C904639DFE380080BCF56438F704F728EC5FBC1D543FFE7B76EB9497D2FE9124308CCD82939B66D478E123DAB08143BAEC36985B374C5FC88F5B41CA1AB2179583DE226BCFE1BA07F82AA38811F389A87DF714881B51EAF2C62E2D77DE24A59EB7935884348A3A61B75938AA5E7FDB5C1CFFD030EF41A18AF91CF96D55B239499826B3924F31F7C7BBF7466C5F69DBF67EB344DDE8E7246610F1B738FF71D14DA35B52D8695CBE8FA334F92431A629F004AC4FBA9400F2465030F19CAE0D34ECD485B143521970D3FB80DCF36B1EB3694946DD57C2016E056487518A3034E7C387AFF01320BADC460F6EDCA1098D07A52A67A73B3EC398A54AB4FA8F0862DC23D6945781943A0651F3A0315EC7349C33A22937512474F1042D5CEA69F3A188CE10E395E19C8A1921E186BC91BA8BEA87753C15699491296AF4DACD2D211B3E98A2CC403F3545A2CED565400364FA1F86F2BF6067A515F2E3AB1F2DF934F7C5E12685C7D87644DCA48E6A0DD272E979CF35E4A0829A494EC779A9F7EEF8C39FC2EB101CABBDA7C1EABDEA92A818B628D265BBE4E08AE1157935968353ADC6B87CD0D7CD2F9F724CDF1FAB5A6CF0B145D559859F05F54477B543D2A6BCBC27C460961A7FEF9F8A3F661B8BB581717CEF42342E4AA7D11A7CF2BFC7C0F13DCC18F5D2222935CD9E26A28EDB13E2C34C496B2D1247A3DC7AABB919D23045545666757775FAD49684ECE444C8B0A8AF1E1FDD17EBEE6270482327CAB00C382526028A48D77039A3147789D326FF407937B6F393641BD6727F10AFA6F5222C502C420355CD1FD8860C6F24459E30FD8DB3BC0830340E2BB4A707DEC0EF6FF40090885474D361888573AB3CB9A17D3CBF895764F202E74369D207055A8F4D2262C317E9B45CFABB34A9FEEC03193D502A9589B6B010CA2F429FBF4377400BEA071428CEA42A3F2C0B08173F279EE1292ACEA8A5C67F8EA6692CB312EF666917E01E731B943DB002F9A89A37099EAE6C1437BF22D5ED82574B6A9265C6A51E4B012B0468B8339EA53C021B670DEF4EC4D955E9F09250EF179654CC9897C685FDE21A4B5A51FF2A0F41C6FAF966EA2D6BCF32B654CA1FA0CD0DF62C78B6411151509B019E69E124B0C079CDF61ED4E534CDDC0A60225117312E2669631E353ADC1890F1E54A0922E340E861FC6C51037133002EB21845EACA15AE968E26418B8D5A88881D7B01906862062A8708D1E8C10F187C76DE78CC9FAF6D210DC4479F3A1D216477D1A1B51796002E572D01E763580CD9425138C1AEE52D847466DD4FAFCB59C7E57415E843EA76BAC9B4F3B2A80597F986222D385B99BC9F599C5BFF0638275485238DE6945F534EF4CC13E97379AD8935056045430A69B785A7878CC425AACC87C8AB35C5AC7DD32EAC31080AE99B0574EA56A2B8260594EE39B90C9536C2C4F22DB40EA71953A30C72C75DB5261C375D3E450BA741FD4EDF0C0A0EFC9D69C7FDABAE473DA7857576DE9112D60E12AE3646F9461A7032C844C41A30074874CE3ABC7505DE15A16698DA0FBC0D2AD86D64A8FD768619EE073EDA689E2430D9ECF3C";
    private static final String sk_87 = "18DFF392DEF5756EA23519A240E6B5CDCF912D89CD94DEC9DC71E53F8CDF37D9D707D38455D5262BE13174016480537CA0E28A167AEAE91598B49FED93B94AD2B66C22023DD7F19B3CD372E14A276A44CEC10D377B88961FCF17C273781D8625AA5E0E304141057CE295AC59B1BB4C3F2B1E42F6873215EB70B6B524B84D46DDC84412E3A0019A020919058164307104017200398421A48548103182A440C228248B984000274C613625081106D9020CD1A848983681DA124E10360511830C1A8641424650D1C2649C3240A1960DD33206A2C8482433298AB6908C3268E3B84D1A32661B31855B348989B461103132640601111688DB2005C3C420A34629032401138909DA1608D0260810A92D5C0468C288800447501C2112C046280C024608104918094ECA100CE396600A29821C490AE1224ED1A20C50448199B00488204D5194919198055922318CA26001270C10872122334CE434001114889B1082033092C3A24422274ECC286101158112414820B570D2B06114172D03C191222842C24626E2B44C23C76022306690B81002B73054340062482E208029A1823012946D84102C9A2290213612DA86701016709A068580244A020869D8A680192182003206413488E14884533091243922D3C2010A236250088D88920919256E01999061A248C1064458280202C90CD2B4691A3622109801DB96699A20910281410C884418044A4C380A44482EDC048202178221440850B245D284010B8670414404DB1461182931E2388D5A264022452A53A20C9406225C908DA3361023952508A12C90108414052854A4280CC560C4B48D60C84424437220103109A350CAC66CCB462A21802D12B364A3B28D638004D8908181106D89066A1B466400B4511B20221493606140698084849140300C3289028285632848A0C220DC2452D21029082281D1203204354612892820474112176AA0104C59928189B8019C181009A644C1362181804921058644820121140802392ADCB82DA0A8210A022892026D24A501C2B861CC120EDC00628920608088701C9265602805DCB608E0920990B20419B520591490219184D0142E82184A0C0224533409C8108A99420A01028A63909120416208294612072E014111D2842881B0841B87604B420A201709DC224412B50D8446454B964942B884CA224051446A19A368E3802909930C8C1860584491020428A0B061A1C004C0000C1AA5845A487101248994169211926851048912A12D8AC6418226248A92401B436D042412C126252293680CB8606018311224640A4308CB3232D4C668A1466A0A886C92084040084802874D0AB25044280541A4712245406194450334418B8429DC1445C9122D88486654C28912383213C5008A966522484D190328C106014B10716304420817010B318119A251A2268E11B3000B930D1232891CA4495AB62100296A634446C4C08123879113B29199864999B22DE1A42522A84D98162483924DDB18890BC74960024454380CCC224C801466E2B4659C08224A9411D2168424B74048028C2141451BB98542C8485B02020109644940724904904C844552946C04392018173220258D81480254B488834468989264123229522230182460D0C271E2067120250022380E92B22884002ECC302513448622B3084C36866086814A40699A8049C21025CA08601499051A402648200C08006AE200421224320428221932901A05905C2831092185180311214112499824A2B88C01078A9B02881C005064368C10226D5A26889906480086655B0892A11045909204A324499B9891C9140D8212521AA4499800920A41201811720C8081A22249C284090B1766212708888421CBB86112486D634840102340C0C641CC402D08906501428C0B942DD4168D89928C893469E4A444D81851E410524CC62008150583124801C84512B829C8102C1C295141C40D58189293060280064E8C9488D03061CC324420C84D49344AC412028808059B940414314913364D54966120366023930DD13450842825A0A45112472E04808511965041324D02806CDA828882362E48B44C4346505A361111081054826021331022492211B44DC8207252489122952CC2207203A665491284830084E1068411C30C08266A1433899346644B469100010480242C144480D2020923488098260C1AB72111C4491182648B82681223692229046BDC8095AA4863C06F03811E2A8848D251CCF80BA1F64DEE2C7C1CDBD1AC37AC42C7B2A660F8C5A828BCFF10F78982AFE4C0ABB6D3AC5B0A475500B5C66250A23255FAFB79B0B5C500DB1290A09621B6772DF1776FF6B0943D6AF53C58168EC57D038FCE12EC6735194448AA2A87D00AB4FB33FAF61B6AB0F6D7A65963F9D46D645A50B7F495C2710215A4F0332ED9B0613B6B90C79C70AE975AA59EFD2CDFA9F777B65608E0A08C7B08790ED85E06F9D2E2E9775AEA198A6A97D5C27437DA0AFA6E11A0CD5E1FBEF1DD5D4430888BA75A5E554D2C2C7406834622979D121E2B59D3CEF3F1C70B3E7E1E0A3403F33FBC528DB80F7C3ECA23A4C357D5AC526D6E200133C01F72CB04EB4D07EA55185CEBAF42D44E7A34C214E531E0E2E8F08D4CD335109C6BABD1E1775E0001C067A42B0C7DE66DEE96CC637A9107C041FD90FFE7929ED210C71C29715C8172C9FBB7A694A41BE8AD3F5F10DAF567D3D6E15C3AAE4051F2693C1E58B08862C8ACF706416A088C3E3C65D9A540A6C65027CFA1A3CD16AA5B18BD3C5F785CECD714EBD858B40EC0E4E7F6790B8EBB495B2A38E04F5E67741CEB9A948E325165348108489843F0FBBF4860B39596A360C3ECFADA3CFD37BB7E025EE8C63B38AC7F0FC2234C427BBD6E21ADECF87C20ABDD4DDC1DAB22F8F207995426733F546BA135A4E4CE50E71D24C86B3E7D0990843CC3FC2EA8E811231AAEB075D1917218BA5C9852ABE28AB34990BC93A6FD7EE338E31A7B90D518D273FCAECE27D58F5A5045547BE5547715FB05EC53C93DD76D2C5FF4F04C4EAD0C8E019D2586F30F7E14AFBFD27D1C4DAFD616900DE1018F74AD5A9522392040D387E5566FD8C3EF7B5A89EFDB39D456CADC6081F7E124098878A3CD580344DB86ABBEDEA7FA349FC0256E6EAFDE4F57D4075486D940560A31CCA1254FCACDDC07A4CB0A57364B4B039C7803834A1E85EA194D796389AF28364242A57CC49F7D4B4F124923D50705BD9D6DA04720B817007CA611DCEDF17DF59CF601D01B30FB660A3A87B8085BEC72D0B30285739BBED124C4F50A3AB572530EACF1F7BECEAF474156398EC3F12BDD0939666691D89529612B7E8FE7B672FD4DBE630121B5F864D9E12C7BC1A953BCF557023A953E031704FF30A814934D368263D9536BF002D00244375DAE22905143A47FFF8777F8C8522241C0E28AEB4C5CBDC156EB45C6762058A2D53C81A29446C128171D13D24A6248341CB7D09B2F535E8BB528DE76D3B1726A3D5E3D6813990916B294945485B7A531076787FF4CA7DBF32D8B26C9613BBE2473680A8FC782843E475949711E3516DD563B8A6CEC6C5E37EFA9563FC9EE4454298077B7148EADB21F898D01452C5BEBDFAE8C9C58620E8553D13D72DBF05A8B82D0EC3EE2176B4FC1FF364FB610B43E766B61C9962BC93F63FF3ABB60A9CFFA19B18DF9C7E588228597B0408299010563CD8098974397FC84D033432860E84EB9B7A9CF1BB3B38C503D59CFAD70A488E8FA63A1D4DA15B87542C6C737C82A5BBF2A59894C4F44330948136492279CE52DDF007F75DCE818D1D58C41483DB70AB975F2D95611CCA577576AD0FBC23D1DD20C63B160DC227D7EB8D41F582170C3C59575F770C5C9CA702F3601D781E25A74C0117035AD742B5E41BE3E1A5711A2D4E17A0228CED21BDC3320C32F3F048476E2440AA1019C7BD076C8E16129325073C93F5E2B2FCF859428B51D827F372E2427848B7B24B42D18FA452B6614DD55B8382F9D1C98ABB0AF1721ABAEB0E7BDF1D6752B130A994B9C940660259B690A0FCEC5AB3668FA41E24C3D1D87962815D739CA341D5C4B687F3DBC0E598432A5C9CE6C78952A79DAEF94925787C34883A16DEEE435711FC41ACA62286C46301A6F22221C57DAD0686D0AFF2DE15D8D258EC01A5756DE75A67246E2F9AD6705A68EBFFE08BBFCF000412A22648A6A84A0B4310D63F460E012710D95C0728176019E361B100C5C531AD55AFF9A6C613BC6AE99456CA03FB2A2A36907FDAA54F91633CD35CE7C9926D46D223386F517874D6905D39A6801FB007C39B36D70A39A4F0E39D8D2EC8594168C3D4ED10C200AB3652C40E1779F582425594CEE70363CADD3E6A3C06F7548A76724A087633DAEE1F87FB4D5C0FC490D57A5B7E4348D9436779639E215E0DCEC31987772AFBEBD6D28B7222C999320E5F8262614207A7C02E07335BC7F69AC76CB5FA2F30607CCB73FD9A926CDE3CF6D31CDFB98D6BB0F55BD2FD7AF2D0C92E5D8883D8B3A471509682B850DDE31305A1D29D501A6E0E094845BB05B0F07C40EBA4FE13BE407CC41F5AEECA943960B0DD05497CAE56E6E22A70993C6B85B49682D5477A9730C4F99EB54D598D9B3F8232031E1812C2473F5C88D6B928ECB126C0FF020540073C0423EF3B44AE35964D64C0210F67095E1E567D7F742DBAE21EFC433EB900B6640C4DE1FB8B1B6655324B54FF38B02BCE7E00BEFF81FBDE5B70DADA4EE409483275AF77B75C844FC8043F6DA74E4BA197E81EF7169D46D70226E29B9F48282C4F10F1BA297369B09FC6DCDEF3ADCB38F0B2C36E0BB5A3CC84DED2925E315380598CF85B915FFE70A6C74E9E1FFAB173B582D187507DD80C7C8D33DCF4200B47AA3B5211D3AE01968349B14D3E985C95F2E25824BFC22CAAB2698E2D568B9C6DCD995E5A17C90D680DEA9EB674487D2A9A8F71AFD28089A3F301CB1EC1F5DA5E70633A8708185485CB95088698ABDBF172E440C7050952081831AFEB3FE91014909D7B8A87489E77C37F17F8D2F670BE73ACCE286EA7D1E33DF403F0EE2EFD5ED412AAD3E0546E122F6435880207B60FD15C5996A996A4E7C129C679C92BDBA9529A17306444C4F523E2CCB7434CA4960EAEE53552EC03ACC0BE4DF7C83476BFC3D446B673672A0B4872C63C6409711EEB9371B95BCFF1A5D89A4B285ED40B55FB6311C6BE8C2B5BE04A387AB67B175348B7401E565FED5B2A3077AE35D9DF95D3A152268CD4B141BD92D886436F9C006A98C271D92764D0EA580BEA79E360F9AA71F1AC6FA568D64C0A091940F872F98BC68776BF1B5364D6207AC697EB4B694BECB289B20081E2C33590726E9011993562DEEAFA3FBB8D51294A358A3C59EFEAAF070A2600E04FE06E6536F8FF3F268B4FA5656D438D2294EF1439E4A07D3EF10DC857F358FDC3F9C99C751901A015C238209AE3B74B9E169E25E212EA44DD7158AB2FE6F9082C613A488CE74FA545A8F5A6B706B8587F12A9E39229DE2069968D700F2A0219BB49B3361D696D5A10CCDFFFBE41F32B38FEDA79C0A86B9B1BED5A087EF7F7AD2D5455083733919D19EF151C5073C9315CBE479F0CA6737861182E80A3B4E0710AD4FF7F2F904C46DA58E7D2FCE62353C45C641D30B0E494927E4D6971F7BC07B853AC40C312798727E80FA4E842DDDA9DFCAD19D1F7DFE059A7EF19EB2CBAE61B8F64E4C9F1B41EB142F2ACB79B44401039F53631BF479944754005EEDD982356B1EACF020E8781FEA794214AF6C174333EE8A2F82F9CF72CF944B21B493BDD2B7B562A4C87D85AA19C35CED4DF069E75057DE578CB92EE6C0F5892BDFC2369C35305180F4E5291B278D56F5E4A851D1949B9747F4C788B3EC0A0B4B68D0020DA8A96C0A79E66BD4094C54CAA6B9A59EBC95BF9056AC16FB8F1C46702A091D1DAD582B27FC7656D84DC1EAB6258E35B78F5016236444B03C1CAA66326B73F786B0C85C7BEE4AA157C6DF9F1C53238010A95515248328CADC3CD69EBE7BBEB87B5AF5D6C16ECB99ADDA7753BB7DE09F3040A1BBAAB040E60CE708FC25E66987B7298201E8C5A0121CD14160A0088752AC499FF01CF361249CD2E436465E99AD4E52D137431A271C56AFDC5D527DA81F9B29B59507B7D0A4D8CB3DE03135A8751098B9A05D7025B32ED98CE72BF621BBE5BDC800765D4B4E295CA02444A9631636A56C2D123589E67DC3C8E06D0C9572DB29E160FF2DA6387B2127382BA891DF5B1D6DB7925D941EAABB55958A089CAC6AFE37C224427D917B61AAB363242C6385E4F1BC811B20AF66089C5B941601F1F551B385DF3D49E920DCD9B5B0F0CF51B54A0D77378B21C1FA0DF2F825173410D292B8017441068BE8B6E7719747705B8AEF7899A60A823F38D72194CDA3C242E67EC95BADAB1F4124C8D2660FA0DE2D29D2BC76341287D8B68597E714D192BA0F15C8F9EE6B06E5243C5E57745EF40A46BD5342FC8F0108CAB434224CCF6E26D86977E7879DC21646122D8D8008BC2E387629C083738CB248A0CCC8642379EDF73B1A2C7A84C90E7394E4B3DEA13C210CCA51544C00D8E568A3688E77A61423FF2304F6091D9FFCB7C7BE873FC40A76C7389B387A407134F952A4B170F68C29A3B55D7E20185CFA2BBF69B2685C6A4246F14C326E736F6883FFFCF5E7365607F199C952A570A4E753B4CE80C03FCCB765BEEA3D964DCAEB643EFA27E7A1BA71A0C13B4CF7B1021E2F9A2E9777DB30258A97314E2AA2BFD82D30D7EC90F4410E65BF89D272BD5EB9C158F3A440F2928474AA0FF4D5C23409F1146021C2510A25815CD5B4EF32F753AB8D95F59CCAC60048AF0A7688E5A3A7CD099DD1C4F0BE826F1CEB7F01A3D5614753A85BAFDC2A4F797B504F07B1C8281E12E4E27C883735D742812EEE497AC0A6C4C9D59D3D602FB0DBDABCC2CE";

    @Test
    public void testKeyGeneration() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            MLDSAGenParameterSpec paramSpec = new MLDSAGenParameterSpec(parameterSet);
            keyGen.initialize(paramSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            assertNotNull(publicKey);
            assertNotNull(privateKey);

            // Test key conversion back to specs
            KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            MLDSAPublicKeySpec pubSpec = keyFactory.getKeySpec(publicKey, MLDSAPublicKeySpec.class);
            MLDSAPrivateKeySpec privSpec = keyFactory.getKeySpec(privateKey, MLDSAPrivateKeySpec.class);
            assertNotNull(pubSpec);
            assertNotNull(privSpec);
        }
    }

    @Test
    public void testSignatureGeneration() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Generate key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            keyGen.initialize(mldsaParameterSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();

            // Sign data
            byte[] data = "Test data".getBytes();
            Signature signer = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            signer.initSign(keyPair.getPrivate());
            signer.update(data);
            byte[] signature = signer.sign();

            // Verify signature
            signer.initVerify(keyPair.getPublic());
            signer.update(data);
            boolean verified = signer.verify(signature);
            assertTrue(verified);

            // Verify with modified data should fail
            data[0] ^= 1;
            signer.update(data);
            verified = signer.verify(signature);
            assertFalse(verified);
        }
    }

    @Test
    public void testMLDSASignatureVerification() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Generate key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            keyGen.initialize(mldsaParameterSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();

            // Test with different data sizes
            testWithData("Test data!", keyPair);  // Short message
            testWithData(String.join("", Collections.nCopies(1000, "A")), keyPair);  // Longer message
        }
    }

    private void testWithData(String dataString, KeyPair keyPair) throws Exception {
        byte[] data = dataString.getBytes(StandardCharsets.UTF_8);

        // Sign the data
        Signature signer = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
        signer.initSign(keyPair.getPrivate());
        signer.update(data);
        byte[] signature = signer.sign();

        // Verify with correct data
        signer.initVerify(keyPair.getPublic());
        signer.update(data);
        assertTrue(signer.verify(signature));

        // Verify with tampered data
        byte[] tamperedData = data.clone();
        tamperedData[0] ^= 0x01;
        signer.initVerify(keyPair.getPublic());
        signer.update(tamperedData);
        assertFalse(signer.verify(signature));

        // Verify with wrong public key
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
        keyGen.initialize(new MLDSAGenParameterSpec("ML-DSA-44"), new SecureRandom());
        KeyPair anotherKeyPair = keyGen.generateKeyPair();
        signer.initVerify(anotherKeyPair.getPublic());
        signer.update(data);
        assertFalse(signer.verify(signature));
    }

    @Test
    public void testSignatureGenerationWithDeterministicFlag() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Generate key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            keyGen.initialize(mldsaParameterSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();

            // Sign data with deterministic
            MLDSASignatureParameterSpec signatureParamSpec = new MLDSASignatureParameterSpec(true, false, false, false, null);
            Signature signer = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            byte[] data = "Test data".getBytes(StandardCharsets.UTF_8);
            signer.setParameter(signatureParamSpec);
            signer.initSign(keyPair.getPrivate());
            signer.update(data);
            byte[] signature = signer.sign();

            signer.initVerify(keyPair.getPublic());
            signer.update(data);
            assertTrue(signer.verify(signature));
        }
    }

    @Test
    public void testMLDSASignatureGenerationWithPreHashFlag() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Generate key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            keyGen.initialize(mldsaParameterSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();

            // signature with preHashed data
            byte[] data = "Test data".getBytes(StandardCharsets.UTF_8);
            MLDSASignatureParameterSpec signatureParamSpec = new MLDSASignatureParameterSpec(false, true, false, false, null);
            Signature signer = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            signer.setParameter(signatureParamSpec);
            signer.initSign(keyPair.getPrivate());
            signer.update(data);
            byte[] signature = signer.sign();

            // verify signature with preHashed data
            Signature verifier = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            verifier.setParameter(signatureParamSpec);
            verifier.initVerify(keyPair.getPublic());
            verifier.update(data);
            assertTrue(verifier.verify(signature));
        }
    }

    @Test
    public void testMLDSASignatureGenerationWithEncodeFlag() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Generate key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            keyGen.initialize(mldsaParameterSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();

            // without context
            byte[] data = "Test data".getBytes(StandardCharsets.UTF_8);
            MLDSASignatureParameterSpec signatureParamSpec1 = new MLDSASignatureParameterSpec(false, false, true, false, null);
            Signature signer1 = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            signer1.setParameter(signatureParamSpec1);
            signer1.initSign(keyPair.getPrivate());
            signer1.update(data);
            byte[] signature1 = signer1.sign();

            Signature verifier1 = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            verifier1.initVerify(keyPair.getPublic());
            verifier1.setParameter(signatureParamSpec1);
            verifier1.update(data);
            assertTrue(verifier1.verify(signature1));

            // with context
            byte[] context = "context".getBytes(StandardCharsets.UTF_8);
            MLDSASignatureParameterSpec signatureParamSpec2 = new MLDSASignatureParameterSpec(false, false, true, false, context);
            Signature signer2 = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            signer2.setParameter(signatureParamSpec2);
            signer2.initSign(keyPair.getPrivate());
            signer2.update(data);
            byte[] signature2 = signer2.sign();

            Signature verifier2 = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            verifier2.initVerify(keyPair.getPublic());
            verifier2.setParameter(signatureParamSpec2);
            verifier2.update(data);
            assertTrue(verifier2.verify(signature2));
        }
    }

    @Test
    public void testMLDSASignatureGenerationWithExternalMuFlag() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Generate key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            keyGen.initialize(mldsaParameterSpec, new SecureRandom());
            KeyPair keyPair = keyGen.generateKeyPair();

            // Sign with external mu
            byte[] data = "D9DFFBBD4191700AD4B00A0BFDC769A70500540A8157AB874F6D114CC35B8F90F86F41F82074554850DD959F5F71509C0748AC4076671F1BD71C3297D9C1E769".getBytes(StandardCharsets.UTF_8);
            MLDSASignatureParameterSpec signatureParamSpec = new MLDSASignatureParameterSpec(false, false, false, true, null);
            Signature signer = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            signer.setParameter(signatureParamSpec);
            signer.initSign(keyPair.getPrivate());
            signer.update(data);
            byte[] signature = signer.sign();

            Signature verifier = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            verifier.setParameter(signatureParamSpec);
            verifier.initVerify(keyPair.getPublic());
            verifier.update(data);
            assertTrue(verifier.verify(signature));
        }
    }

    /**
     * Converts a hex string to a byte array.
     * @param hexString the hex string to convert
     * @return the byte array
     * @throws IllegalArgumentException if the string is not a valid hex string
     */
    private static byte[] hexToBytes(String hexString) {
        if (hexString == null || hexString.length() % 2 != 0) {
            throw new IllegalArgumentException("Invalid hex string");
        }
        return HexFormat.of().parseHex(hexString);
    }

    @Test
    public void testMLDSASignatureGenerationWithGivenKeyData() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // convert hex string key data to byte array
            byte[] pubKey;
            byte[] privKey;
            if (parameterSet.equals("ML-DSA-44")) {
                pubKey = hexToBytes(pk_44);
                privKey = hexToBytes(sk_44);
            } else if (parameterSet.equals("ML-DSA-65")) {
                pubKey = hexToBytes(pk_65);
                privKey = hexToBytes(sk_65);
            } else {
                pubKey = hexToBytes(pk_87);
                privKey = hexToBytes(sk_87);
            }

            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Initialize public and private key
            MLDSAPublicKeySpec pubKeySpec = new MLDSAPublicKeySpec(pubKey, mldsaParameterSpec);
            MLDSAPrivateKeySpec privKeySpec = new MLDSAPrivateKeySpec(privKey, mldsaParameterSpec);

            // Generate public and private key
            KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
            PrivateKey privateKey = keyFactory.generatePrivate(privKeySpec);

            byte[] data = "Test message for ML-DSA signature".getBytes(StandardCharsets.UTF_8);

            // Sign data
            Signature signer = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            signer.initSign(privateKey);
            signer.update(data);
            byte[] signature = signer.sign();

            // Verify the signature
            Signature verifier = Signature.getInstance("SHA256withMLDSA", HiTls4jProvider.PROVIDER_NAME);
            verifier.initVerify(publicKey);
            verifier.update(data);
            assertTrue("Signature verification failed for " + parameterSet, verifier.verify(signature));
        }
    }

    @Test
    public void testKeyRestore() throws Exception {
        for (String parameterSet : SUPPORTED_PARAMETERSETS) {
            // convert hex string key data to byte array
            byte[] pubKey;
            byte[] privKey;
            if (parameterSet.equals("ML-DSA-44")) {
                pubKey = hexToBytes(pk_44);
                privKey = hexToBytes(sk_44);
            } else if (parameterSet.equals("ML-DSA-65")) {
                pubKey = hexToBytes(pk_65);
                privKey = hexToBytes(sk_65);
            } else {
                pubKey = hexToBytes(pk_87);
                privKey = hexToBytes(sk_87);
            }

            // Initialize ML-DSA parameters
            AlgorithmParameters params = AlgorithmParameters.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            params.init(new MLDSAGenParameterSpec(parameterSet));
            MLDSAParameterSpec mldsaParameterSpec = params.getParameterSpec(MLDSAParameterSpec.class);

            // Initialize public and private key
            MLDSAPublicKeySpec pubKeySpec = new MLDSAPublicKeySpec(pubKey, mldsaParameterSpec);
            MLDSAPrivateKeySpec privKeySpec = new MLDSAPrivateKeySpec(privKey, mldsaParameterSpec);

            // Generate public and private key
            KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", HiTls4jProvider.PROVIDER_NAME);
            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
            PrivateKey privateKey = keyFactory.generatePrivate(privKeySpec);

            assertNotNull("Public key should not be null for " + parameterSet, publicKey);
            assertNotNull("Private key should not be null for " + parameterSet, privateKey);

            // Test key conversion back to specs
            MLDSAPublicKeySpec pubSpecResult = keyFactory.getKeySpec(publicKey, MLDSAPublicKeySpec.class);
            MLDSAPrivateKeySpec privSpecResult = keyFactory.getKeySpec(privateKey, MLDSAPrivateKeySpec.class);

            assertArrayEquals("Public key data should match for " + parameterSet, pubKey, pubSpecResult.getEncoded());
            assertArrayEquals("Private key data should match for " + parameterSet, privKey, privSpecResult.getEncoded());
        }
    }

}